package org.homeunix.josivan.gwt.tool.gtic;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Path.PathElement;
import org.homeunix.josivan.gwt.tool.gtic.StatusFileHandler.ModuleData;

import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.Compiler;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;

public class GWTIncrementalCompiler extends Task {
  private AbstractTreeLogger logger;

  //private String gwtSourceDir;
  private String gwtOutputDir;
  private String initialPackageName;
  //private File sourceDir;
  private File outDir;
  private StatusFileHandler statusFileHandler;
  private GwtXmlFileHandler gwtXmlHandler;
  private List<ModuleData> modulesToCompile = new LinkedList<ModuleData>();
  private Vector<DirSet> dirSets = new Vector<DirSet>();

  @Override
  public void execute() throws BuildException {
    try {
      super.execute();
      log("Initing GWTIncrementalCompiler...");
      this.validateRequired();
      this.validatePathExists();

      this.gwtXmlHandler = new GwtXmlFileHandler(this.listSources(), this.outDir,
        this.initialPackageName);
      this.gwtXmlHandler.execute();

      this.statusFileHandler = new StatusFileHandler(this.outDir);

      doUpdateStatusModulesFile();
      doUpdateLastModificationAndCompilationTime();
      loadModulesToCompile();
      doCompile();
    } 
    catch (IOException e) {
      throw new BuildException(e);
    } 
    catch (UnableToCompleteException e) {
      throw new BuildException(e);
    }
  }

  /*
  public void setGwtSourceDir(String gwtSourceDir) {
    this.gwtSourceDir = gwtSourceDir;
  }
  */

  public void setGwtOutputDir(String gwtOutputDir) {
    this.gwtOutputDir = gwtOutputDir;
  }

  public void setInitialPackageName(String initialPackageName) {
    this.initialPackageName = initialPackageName;
  }
  
  public void addDirSet(DirSet dirSet) {
    this.dirSets.add(dirSet);
  }
  
  /*
  public void addClassPath(Path path) {
    log("Josivan " + path);
  }
  */

  private void validateRequired() {
    log("Verifyng required Parameters...");

    /*
    if (stringUndefined(this.gwtSourceDir)) {
      throw new BuildException(messageUndefinedParameter("gwtSourceDir"));
    }
    */
    
    if (this.dirSets.isEmpty()) {
      throw new BuildException(messageUndefinedParameter("Filesets from source"));
    }

    if (stringUndefined(this.gwtOutputDir)) {
      throw new BuildException(messageUndefinedParameter("gwtOutputDir"));
    }

    if (stringUndefined(this.initialPackageName)) {
      throw new BuildException(messageUndefinedParameter("initialPackageName"));
    }
  }

  private void validatePathExists() {
    //this.sourceDir = new File(this.gwtSourceDir);
    this.outDir = new File(this.gwtOutputDir);

    for (DirSet dirSet : this.dirSets) {
      File file = dirSet.getDir();
      existsDirectory(file);
      isDirectory(file);
    }
    
    createOutputDirIfNotExists(this.outDir);
    isDirectory(this.outDir);
  }

  private boolean existsDirectory(File file) {
    if (file.exists()) {
      return true;
    }

    throw new BuildException(file.getName() + " not exists.");
  }

  private void createOutputDirIfNotExists(File file) {
    if (!file.exists()) {
      try {
        FileUtils.forceMkdir(file);
        log(file.toString() + " not exists. Created with sucess...");
      } catch (IOException e) {
        log(file.toString() + " not exists. Failure on create...");
        throw new BuildException(e);
      }
    }
  }

  private boolean isDirectory(File file) {
    if (file.isDirectory()) {
      return true;
    }

    throw new BuildException(file.getName()
        + " exists, but is not a directory.");
  }

  private String messageUndefinedParameter(String parameterName) {
    return String.format("%s is undefinied.", parameterName);
  }

  private boolean stringUndefined(String s) {
    return "".equals(s) || s == null;
  }

  private void doUpdateStatusModulesFile() {
    doVerifyModuleStatus();
    doCompleteNoCompilableModules();
    this.statusFileHandler.doUpdateStatusFile();
  }

  private void doVerifyModuleStatus() {
    log("Verifyng modules status...");

    Set<String> compilables = this.gwtXmlHandler
        .getCompilableModuleDependencies().keySet();
    Properties status = StatusFileHandler.MODULES_STATUS;

    for (String compilableModule : compilables) {
      String property = status.getProperty(compilableModule);

      if (property == null) {
        status.put(compilableModule, "Y|0|-1|0|N");
      }
    }
  }

  private void doCompleteNoCompilableModules() {
    log("Loading module dependencies...");
    Set<String> noCompilables = new TreeSet<String>();
    Map<String, List<String>> modules = this.gwtXmlHandler
        .getCompilableModuleDependencies();

    for (List<String> inherits : modules.values()) {
      noCompilables.addAll(inherits);
    }

    Properties status = StatusFileHandler.MODULES_STATUS;

    for (String noCompilable : noCompilables) {
      if (!modules.containsKey(noCompilable)) {
        String propery = status.getProperty(noCompilable);
        if (propery == null) {
          status.put(noCompilable, "N|-1");
        }
      }
    }
  }

  private void doUpdateLastModificationAndCompilationTime() {
    log("Updating last modification and compilation time...");
    Enumeration<Object> keys = StatusFileHandler.MODULES_STATUS.keys();
    Map<String, File> files = this.gwtXmlHandler.getModuleFile();

    while (keys.hasMoreElements()) {
      String module = keys.nextElement().toString();
      ModuleData data = new ModuleData(module);
      File file = files.get(module);
      long lastModificationTime = this.gwtXmlHandler
          .getMajorDateOfFilesInFolder(file);
      data.setLastModificationTime(lastModificationTime);

      if (data.isCompilable()) {
        long lastCompilationTime = this.gwtXmlHandler
            .getLastCompilationTime(module);
        data.setLastCompilationTime(lastCompilationTime);
      }

      data.store();
    }

    this.statusFileHandler.doUpdateStatusFile();
  }

  private void loadModulesToCompile() {
    Enumeration<Object> keys = StatusFileHandler.MODULES_STATUS.keys();
    while (keys.hasMoreElements()) {
      ModuleData data = new ModuleData(keys.nextElement().toString());

      if (data.recompile()) {
        this.modulesToCompile.add(data);
      }
    }
  }

  private void doCompile() throws UnableToCompleteException {
    IncrementalCompilerOptions options = new IncrementalCompilerOptions(
        /*this.sourceDir, */this.outDir);
    options.setLogLevel(AbstractTreeLogger.ERROR);
    this.logger = 
      new PrintWriterTreeLogger();//new PrintWriter(new File(this.outDir, "gwt.tree.compilee")));

    String msg = "%s on compile module %s";

    for (ModuleData data : this.modulesToCompile) {
      long start = System.currentTimeMillis();
      options.addModuleName(data.module);
      Compiler compiler = new Compiler(options);
      boolean sucess = compiler.run(this.logger);
      long end = System.currentTimeMillis();

      String text = "";

      if (sucess) {
        text = String.format(msg, "Success", data.module + " (" + (end - start)
            + " ms).");
      } else {
        text = String.format(msg, " Error", data.module);
      }

      data.setLastCompilationTime(end);
      data.store();
      log(text);
    }

    this.statusFileHandler.doUpdateStatusFile();

    log("End...");
  }
  
  private File[] listSources() {
    File[] files = new File[this.dirSets.size()];

    for (int i = 0; i < this.dirSets.size(); i++) {
      files[i] = this.dirSets.get(i).getDir();
    }
    return files;
  }
}
